//#include <X11/Xlib.h>
//#include <X11/Xutil.h>
#include <cairo.h>
#include <cairo-xcb.h>
#include <xcb/xcb_aux.h>  // xcb_aux_get_visualtype
#include <xcb/xcb_atom.h>
#include <xcb/xcb_icccm.h>
#include <X11/keysym.h>

#include "mcp.h"
#include "ui_cairo.h"
#include "uic_xcb.h"

namespace uic
{

// Hopefully-common starting values until these are obtained the Right Way.
GenericEvent::mod_values GenericEvent::mask =
    { XCB_MOD_MASK_SHIFT, XCB_MOD_MASK_LOCK, XCB_MOD_MASK_CONTROL,
      XCB_MOD_MASK_1, XCB_MOD_MASK_2, XCB_MOD_MASK_3, XCB_MOD_MASK_4, XCB_MOD_MASK_5 };

UIC_Xcb::UIC_Xcb ()
    : c(0), keysyms(0), surface(0)
    {}

UIC_Xcb::~UIC_Xcb ()
    {
    if ( surface ) cairo_surface_destroy(surface);
    if ( keysyms ) xcb_key_symbols_free(keysyms);
    if ( c ) xcb_disconnect(c);
    }

cairo_t *UIC_Xcb::get_cairo (CairoUI *ui)
    {
    assert(surface != 0);
    return cairo_create(surface);
    }

void UIC_Xcb::resized (CairoUI *ui)
    {
    if ( surface )
        cairo_surface_destroy(surface);
//        cairo_xcb_surface_set_size(surface, ui->width, ui->height);
//    else
        {
        xcb_visualtype_t *visual;
        visual = xcb_aux_get_visualtype(c, screen_number, screen->root_visual);
        surface = cairo_xcb_surface_create(c, window, visual, 
                                           ui->width, ui->height);
        if ( cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS )
            {
            mcp->log_verbose(1, "Creating cairo xcb surface: %s\n",
                             cairo_status_to_string(cairo_surface_status(surface)));
            // TODO  abort, retry, or ignore?
            }
        }
    }

void UIC_Xcb::set_title (const char *str)
    {
    if ( !str ) str = "";
    xcb_change_property(c, XCB_PROP_MODE_REPLACE, window,
                        WM_NAME, STRING, 8, strlen(str), str);
    }

void UIC_Xcb::perform_flush (CairoUI *ui)
    { xcb_flush(c); }

bool UIC_Xcb::active ()
    { return ( c != 0 ); }

// TODO  trap X errors for window creation n' such
bool UIC_Xcb::activate (CairoUI *ui)
    {
    int req_x, req_y;
    int req_width, req_height;
    size_hints_t *size;
//    xcb_void_cookie_t cookie;

    if ( !(c = xcb_connect(0, &screen_number)) )
        {
        fprintf(stderr, "rmc-xmms2: xcb_connect: %s\n", strerror(errno));
        return false;
        }

    if ( ui->fd ) delete ui->fd;
    ui->fd = new int[1];
    ui->fd[0] = xcb_get_file_descriptor(c);
    ui->fd_count = 1;

    screen = xcb_aux_get_screen(c, screen_number);
/*
    xcb_screen_iterator_t iter;
    int tmp = screen_number;
    iter = xcb_setup_roots_iterator(xcb_get_setup(c));
    for ( ; iter.rem; tmp--, xcb_screen_next(&iter) )
        if (tmp == 0)
            {
            screen = iter.data;
            break;
            }
*/

    keysyms = xcb_key_symbols_alloc(c);
    // TODO  key matching structure rather than keeping keysyms loaded, since
    //       we only match a small number of keys
    // xcb_key_symbols_free(keysyms);

    // TODO setup XAddConnectionWatch etc. here
    //      ?  is it still needed
    //      -  I can't test it if it is..

    req_x = 0;
    req_y = 0;
    req_width = 160;
    req_height = 100;
    ui->apply_prefs();
    {
    const char *p;
    if ( (p = ui->prefs->get("geometry")) != 0 )
        {
        if ( sscanf(p, "%dx%d+%d+%d", &req_width, &req_height, &req_x, &req_y) == 4 ||
             sscanf(p, "%dx%d", &req_width, &req_height) == 2 )
            mcp->log_verbose(10, "config geometry: %dx%d+%d+%d\n",
                             req_width, req_width, req_x, req_y);
        }
    if ( (p = ui->prefs->get("size")) != 0 )  // eh.. why not
        {
        if ( sscanf(p, "%dx%d", &req_width, &req_width) == 2 )
            mcp->log_verbose(10, "config size: %dx%d\n", req_width, req_width);
        }
    }

    uint32_t cw_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
    uint32_t valwin[] =
        {   screen->black_pixel,
            XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY |
                XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
                XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
                XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
                XCB_EVENT_MASK_POINTER_MOTION |
                XCB_EVENT_MASK_VISIBILITY_CHANGE
        };

    window = xcb_generate_id(c);
    xcb_create_window(c, XCB_COPY_FROM_PARENT,
                      window, screen->root,
                      req_x, req_y, req_width, req_height, 0,
                      XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual,
                      cw_mask, valwin);

    xcb_change_property(c, XCB_PROP_MODE_REPLACE, window,
                        WM_CLASS, STRING, 8, strlen("Rmc"), "Rmc");
    xcb_change_property(c, XCB_PROP_MODE_REPLACE, window,
                        WM_NAME, STRING, 8, strlen(ui->default_window_title),
                        ui->default_window_title);
    xcb_change_property(c, XCB_PROP_MODE_REPLACE, window,
                        WM_ICON_NAME, STRING, 8, strlen("rmc"), "rmc");

    size = alloc_size_hints();
    size_hints_set_min_size(size, 64, 48);
    size_hints_set_base_size(size, 160, 100);
    // does it do this itself?
    size_hints_set_flag_p_min_size(size);
    size_hints_set_flag_p_base_size(size);
    if ( req_x != 0 && req_y != 0 )
        {
        size_hints_set_position(size, 1, req_x, req_y);
        size_hints_set_flag_p_position(size);
        }
    // set_wm_size_hints(c_t, window, ?property?, size);
    set_wm_normal_hints(c, window, size);
    free_size_hints(size);

    xcb_map_window(c, window);
    xcb_flush(c);

    // these can happen in their own time
    set_wm_icon(ui);
    set_net_wm_icon(ui);

    // TODO  configure properly as the window is mapped/unmapped
    ui->width = 0;  /// first_configure
    ui->height = 0; //  kludge

    return true;
    }

/*
void CairoUI::fd_output (int fd)
    {
    flush();
    }
*/

void UIC_Xcb::fd_exception (CairoUI *ui, int fd_arg)
    {
    if ( surface ) cairo_surface_destroy(surface);
    surface = 0;
    if ( c ) xcb_disconnect(c);
    c = 0;
    if ( ui->fd ) delete ui->fd;
    ui->fd_count = 0;
    }

void UIC_Xcb::process_input (CairoUI *ui, int fd_arg)
    {
    xcb_generic_event_t *ge;

    while ( (ge = xcb_poll_for_event(c)) != 0 )
        {
        switch ( ge->response_type & ~0x80 )  // What's the & for here?
            {
            case XCB_EXPOSE:
                if ( ui->width == 0 || ui->height == 0 ) /// Can occur with the
                    break;                               //  first window move
                ui->redraw(false); // not true until we can skip.
                break;
            case XCB_ENTER_NOTIFY:  ui->pointer_enter();
                break;
            case XCB_LEAVE_NOTIFY:  ui->pointer_leave();
                break;
            case XCB_KEY_PRESS:     key_press(ui, (xcb_key_press_event_t *)ge);
                break;

            // Convert the following into the local GenericEvent struct
            case XCB_CONFIGURE_NOTIFY:
            case XCB_BUTTON_PRESS:
            case XCB_BUTTON_RELEASE:
            case XCB_MOTION_NOTIFY:
                {
                GenericEvent gev;
                switch ( ge->response_type & ~0x80 )
                    {
                    case XCB_CONFIGURE_NOTIFY:
                        // TODO skip multiple resizes.. somehow
                        //while ( XCheckTypedWindowEvent(c, window,
                        //            XCB_CONFIGURE_NOTIFY, &ev) )
                      { xcb_configure_notify_event_t *ev =
                            (xcb_configure_notify_event_t *)ge; 
                        gev.x = ev->x; gev.y = ev->y;
                        gev.width = ev->width; gev.height = ev->height;
                      }
                        if ( ui->width == 0 && ui->height == 0 )
                            ui->first_configure(&gev);
                        else
                            ui->configure(&gev);
                        break;

                    // TODO deal with other mouse button configurations
                    //      (side buttons vs. wheel for some actions)
                    case XCB_BUTTON_PRESS:
                      {
                        xcb_button_press_event_t *ev = 
                            (xcb_button_press_event_t *)ge;
if ( ev->state & XCB_MOD_MASK_LOCK )
  mcp->log_verbose(11, "button modifier: Lock \n");
if ( ev->state & XCB_MOD_MASK_1 )
  mcp->log_verbose(11, "button modifier: Mask1 \n");
if ( ev->state & XCB_MOD_MASK_2 )
  mcp->log_verbose(11, "button modifier: Mask2 \n");
if ( ev->state & XCB_MOD_MASK_3 )
  mcp->log_verbose(11, "button modifier: Mask3 \n");
if ( ev->state & XCB_MOD_MASK_4 )
  mcp->log_verbose(11, "button modifier: Mask4 \n");
if ( ev->state & XCB_MOD_MASK_5 )
  mcp->log_verbose(11, "button modifier: Mask5 \n");
                        gev.x = ev->event_x; gev.y = ev->event_y;
                        gev.button = ev->detail;
                        gev.modifier = ev->state;
                      }
                        ui->button_press(&gev);
                        break;

                    case XCB_BUTTON_RELEASE:
                      { xcb_button_release_event_t *ev = 
                            (xcb_button_release_event_t *)ge;
                        gev.x = ev->event_x; gev.y = ev->event_y;
                        gev.button = ev->detail;
                        gev.modifier = ev->state;
                      }
                        ui->button_release(&gev);
                        break;

                    case XCB_MOTION_NOTIFY:
                      { xcb_motion_notify_event_t *ev = 
                            (xcb_motion_notify_event_t *)ge;
                        gev.x = ev->event_x; gev.y = ev->event_y;
                        gev.modifier = ev->state;
                      }
                        ui->motion(&gev);
                        break;
                    }
                }
            }

        free(ge); ge = 0;
        }
    ui->flush();
    }

void UIC_Xcb::move (int new_x, int new_y)
    {
    uint32_t values[] = { new_x, new_y };
    xcb_configure_window(c, window,
                         XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
    }

// FIXME  convert to a GenericEvent and pass to one common key handler
void UIC_Xcb::key_press (CairoUI *ui, xcb_key_press_event_t *ev)
    {
    // ev->  xcb_keycode_t detail;
    xcb_keysym_t keysym;

    // TODO some magic to determine whether to use col 0, 1, 2, 3..
    //      -  Ignore shifted keys for now -- possibly always for this client.
    keysym = xcb_key_press_lookup_keysym(keysyms, ev, 0);
mcp->log_verbose(5, "key press keysym: %d (%x)\n", keysym, keysym);

    switch ( keysym )
        {
        case XK_X: case XK_x:
        case XK_Return: case XK_KP_Enter:
        case XK_Execute:
            mcp->command(COMMAND_PLAY);
            break;
        case XK_C: case XK_c:
        case XK_space: case XK_Pause: case XK_KP_5:
            mcp->command(COMMAND_PAUSE);
            break;
        case XK_V: case XK_v: case XK_KP_Delete:
            mcp->command(COMMAND_STOP);
            break;
        case XK_B: case XK_b: case XK_Right:
        case XK_KP_Right: case XK_KP_Add:
            mcp->command(COMMAND_NEXT);
            break;
        case XK_Z: case XK_z:
        case XK_BackSpace: case XK_Left:
        case XK_KP_Left: case XK_KP_Subtract:
            mcp->command(COMMAND_PREVIOUS);
            break;
        case XK_Home: case XK_KP_Home: case XK_Begin:
            mcp->command(COMMAND_FIRST);
            break;
        case XK_End: case XK_KP_End: 
            mcp->command(COMMAND_LAST);
            break;
        case XK_Up: case XK_KP_Up:
            mcp->change_volume(5, false);
            break;
        case XK_Down: case XK_KP_Down:
            mcp->change_volume(-5, false);
            break;
        }
    }

// I am uncertain whether this is a bug, but on my setup the icon in WM_HINTS
// and _NET_WM_ICON have to be the same size or it ignores the WM_HINTS icon.
// Which leads to the question of why you need to set both..
#define ICON_SIZE 64

void UIC_Xcb::set_wm_icon (CairoUI *ui)
    {
    cairo_t         *cr;
    cairo_surface_t *surf;
    xcb_pixmap_t     pixmap;
    xcb_pixmap_t     mask;
    wm_hints_t      *hints;

    pixmap = xcb_generate_id(c);
    mask = xcb_generate_id(c);

    xcb_create_pixmap(c, screen->root_depth, pixmap, window,
                      ICON_SIZE, ICON_SIZE);
    xcb_create_pixmap(c, screen->root_depth, mask, window,
                      ICON_SIZE, ICON_SIZE);

    surf = cairo_xcb_surface_create_for_bitmap(c, pixmap, screen, ICON_SIZE, ICON_SIZE);
    cr = cairo_create(surf);
    cairo_scale(cr, ICON_SIZE/100.0, ICON_SIZE/100.0);
    ui->draw_icon(cr, false);
    cairo_surface_destroy(surf);
    cairo_destroy(cr);

    surf = cairo_xcb_surface_create_for_bitmap(c, mask, screen, ICON_SIZE, ICON_SIZE);
    cr = cairo_create(surf);
    cairo_scale(cr, ICON_SIZE/100.0, ICON_SIZE/100.0);
    ui->draw_icon(cr, true);

    hints = alloc_wm_hints();
    wm_hints_set_icon_pixmap(hints, pixmap);
    wm_hints_set_icon_mask(hints, mask);
    set_wm_hints(c, window, hints);
    cairo_surface_destroy(surf);
    cairo_destroy(cr);

    xcb_free_pixmap(c, pixmap);
    xcb_free_pixmap(c, mask);
    free(hints);
    }

void UIC_Xcb::set_net_wm_icon (CairoUI *ui)
    {
    cairo_t         *cr;
    cairo_surface_t *surf;
    int              stride;
    unsigned char   *data;
    xcb_atom_t       _NET_WM_ICON;  // compile error if this has been defined

    // set the net_wm_icon atom
    {
    xcb_generic_error_t *err;
    xcb_intern_atom_cookie_t cookie;
    xcb_intern_atom_reply_t *reply;

    cookie = xcb_intern_atom(c, 0,
                             strlen("_NET_WM_ICON"), "_NET_WM_ICON");
    reply = xcb_intern_atom_reply(c, cookie, &err);
    if ( reply )
        {
        _NET_WM_ICON = reply->atom;
        free(reply);
        }
    else
        {
        mcp->log_verbose(2, "xcb error getting _NET_WM_ICON atom: #%d\n",
                         err->error_code);
        return;
        }
    }

    surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ICON_SIZE, ICON_SIZE);
    cr = cairo_create(surf);
    cairo_scale(cr, ICON_SIZE/100.0, ICON_SIZE/100.0);
    ui->draw_icon(cr);
    cairo_surface_flush(surf);

    stride = cairo_image_surface_get_stride(surf);
    data = cairo_image_surface_get_data(surf);
/*
FILE *fl;
fl = fopen("test", "w");
int i = 0;
for (int j = 0; j < ICON_SIZE; j++) {
fprintf(fl, "%3d:", j);
for (int k = 0; k < ICON_SIZE*4; k+=4) {
i += 4;
fprintf(fl, "  %d,%d,%d,%d", data[i], data[i+1], data[i+2], data[i+3]);
}
fprintf(fl, "\n");
}
fclose(fl);
*/
    int32_t *icondata;
    icondata = new int32_t[2 + ICON_SIZE * ICON_SIZE];
    icondata[0] = ICON_SIZE;
    icondata[1] = ICON_SIZE;
    for ( int y = 0, i = 2; y < ICON_SIZE; y++ )
        {
        unsigned char *p = data + (y * stride);
        for ( int x = 0; x < ICON_SIZE*4; x += 4 )
            {
            unsigned char a, r, b, g;
//#ifdef __LITTLE_ENDIAN
#if __BYTE_ORDER == __BIG_ENDIAN
            a = p[x], r = p[x+1], g = p[x+2], b = p[x+3];
#else
            b = p[x], g = p[x+1], r = p[x+2], a = p[x+3];
#endif
            icondata[i] = a << 24 | r << 16 | g <<  8 | b;
            i++;
            }
        }

    xcb_change_property(c, XCB_PROP_MODE_REPLACE, window,
                        _NET_WM_ICON, CARDINAL, 32,
                        2 + ICON_SIZE * ICON_SIZE, (unsigned char *)icondata);

    cairo_surface_destroy(surf);
    cairo_destroy(cr);
    delete icondata;
    }

} // namespace uic
